【Android TimeCat】 Android中使用矢量图(SVG, VectorDrawable)
背景
TimeCat项目中需要根据不同的场景动态改变图标颜色,如果用png等格式,会使图片资源过多。明明图案是一样的,为什么改变个颜色就得多一张图?如果整体颜色风格改变,那之前的图片资源就都废了?所以选择用xml文件来描述图片颜色,想用什么色就用什么色。
图片本质上是一个存像素点的矩阵,而svg高级一点,存一些点,比如一个圆,那么就存圆心和半径数据就行了,这是轨迹,然后再规定颜色,这样和png资源相比,内存大大减少,还容易自定义,改个图标颜色简直不要太方便!
SVG 和 VectorDrawable
SVG
可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。SVG由W3C制定,是一个开放标准。——摘自维基百科
.svg
格式相对于.jpg
、.png
甚至.webp
具有较多优势,我认为核心有两点:
- 省时间。图像与分辨率无关,收放自如,适配安卓机坑爹的分辨率真是一劳永逸;
- 省空间。体积小,一般复杂图像也能在数KB搞定,图标更不在话下。
VectorDrawable
VectorDrawable
是Google从Android 5.0开始引入的一个新的Drawable
子类,能够加载矢量图。到现在通过support-library
已经至少能适配到Android 4.0了(通过AppBrain统计的Android版本分布来看,Android 4.1以下(api<15)几乎可以不考虑了)。Android中的VectorDrawable
只支持SVG的部分属性,相当于阉割版。
它虽然是个类,但是一般通过配置xml再设置到要使用的控件上。在Android工程中,在资源文件夹res/drawable/
的目录下(没有则需新建),通过<vector></vector>
标签描述,例如svg_ic_arrow_right.xml
:
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
基本属性说明:
width
,height
:图片的宽高。可手动修改到需要尺寸;viewportHeight
,viewportWidth
:对应将上面height width等分的份数。以svg_ic_arrow_right.xml举例,可以想象将长宽都为8dp的正方形均分为24x24的网格,在这个网格中就可以很方便地描述点的坐标,图像就是这些点连接起来构成的。fillColor
:填充颜色。最好直接在这里写明色值#xxxxxxxx
,而不要用@color/some_color
的形式,避免某些5.0以下机型可能会报错。pathData
:在2中描述的网格中作画的路径。具体语法不是本文的重点,故不展开。
下面这段代码描述出来的是一个蓝色闹钟,可以从Android Studio的preview
功能栏里预览到它的样子:
emm…既然xml资源作图标这么方便,应该怎么获取呢?
获取矢量图方式一:Android Studio
的Material Icon
鼠标选中drawable
文件夹,右键, New
, Vector Asset
然后出现:
点击机器人进入搜索筛选:
左侧的搜索和分类可以快速索引。这里都是由谷歌官方制作的MD标准图标,建议先到这里搜索,如果没有再到网上搜索。
获取矢量图方式二:iconfont
墙裂安利一个网站,阿里的iconfont,海量在线矢量图,早收藏早致富!我已经离不开它了= ̄ω ̄=
第一步,搜索你要的资源名字,中英文一般都会有结果。比如“arrow”,结果:
第二步,鼠标移动到某一图标上点击,比如上面第一排第二个,出现:
三个选项,第一相当于购物车,可不用登录,第二是收藏,第三是下载,均需要登录。如果未登录,点击后出现:
选择GitHub或微博都行。
第三步,登录成功,点击下载,弹出:
可以对图标属性进行编辑,如色值和大小(单位dp),然后点按钮“SVG下载”。下载成功后在下载目录找到一个.svg格式的文件,这个文件可以用浏览器打开->查看网页源码,或者用NotePad++等编辑器打开看到里面的内容,格式化后是这样:
1 | "1.0" standalone="no" xml version= |
文件里好多标签Android是不认识的。不过没关系,有三种解决办法
手动转化成xml
新建一个<vector></vector>
标签的xml文件
,通过观察文件内容,很容易获取到关键信息。
width
,height
自然对应<vector/>
中宽高,viewBox
后两位数字是分别对应<vector/>
中的viewportWidth
和viewportHeight
,<path/>
中的d
的数据的对应<vector/>
中<path/>
中的pathData
。fillColor
自己手动设置。
svgtoandroid插件
安装:File -> Setting -> Plugins -> Browser repositories -> 搜“svg2VectorDrawable” -> 安装并重启Android Studio,再次进来后顶部工具栏会多一个图标:
点击图标弹出对话框:
勾选Batch选项,将对被选中文件夹中的.svg文件进行批量转换。nodpi会自动添加到没有后缀的drawable文件夹中。
网上下载的svg资源往往一步到位,有个这个插件将会事半功倍。导入第一个svg文件时就命名成我们想要的名字,如果不满意再导入时无需再关注命名,将后面导入的pathData覆盖第一个观察效果,直到满意后删除不需要的文件。
Android Studio自带转化
鼠标选中drawable文件夹,右键, New, Vector Asset, Local file,然后出现:
先选本地文件(还能支持PSD,强吧),再到磁盘中找到之前下载的.svg矢量图。导入后可以为文件重命名(建议用svg_或者有区别于其它格式的前缀),默认导入宽高均为24dp,选中Override框则读取文件本来宽高,其它配置视需求而定。点击Next到下一页最后点Finish就导入了。自动导入需要格式化一下就是前面svg_ic_arrow_right.xml的样子了。
海搜比较耗时间,线条粗细啦,位置没居中啦,大小不搭配啦,关键是这些问题都是导入项目或者运行到手机后才能发现(非强迫症当我没说)。
iconfont还有诸多成套的图标库,优点是风格大小一致,或者多彩图标。
项目应用
前提:
项目的build.gradle
配置有:1
2
3
4
5
6
7
8
9
10
11
12
13android{
...
defaultConfig {
...
vectorDrawables.useSupportLibrary = true
}
...
}
dependencies {
...
compile "com.android.support:appcompat-v7:21+" // 至少Api21
...
}
项目的Activity中都包含(通用做法是在BaseActivity中加):1
2
3static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
AppCompatImageView
这是继承自ImageView用于5.0以下加载矢量图的控件,只需要替换src为srcCompat属性,其它没什么不同。例如:1
2
3
4<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/svg_ic_arrow_right"/>
如果你的Activity
直接或间接继承自AppCompatActivity
,当前视图中的ImageView
在编译过程中会被自动转为AppCompatImageView
(support包
中所有含有AppCompat
前缀的控件均受相同处理),因而在Activity
中通过findViewById()
的实例用ImageView
或AppCompatActivity
接收是没有区别的。
用以上条件的Activity
中装载的Fragment
,或者通过动态注入(如Dialog
的contentView
)的ImageView
,均将被自动转为AppCompatActivity
。
从xml
文件中初始化ImageView
并加载矢量图,必须使用AppCompatImageView
的srcCompat
属性。ImageView
的染色属性tint同样适合矢量图。
TextView
在我的经验中,TextView
可以用到矢量图的场景是最多的,主要是设置CompoundDrawable
。例如:1
2
3
4
5
6<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableRight="@drawable/svg_ic_arrow_right"
android:drawablePadding="4dp"
android:text="drawable right"/>
这样设置后,没有任何不适,编译器也不报错,可能你自己运行也没问题。但是!这才是深坑啊。5.0以下某些机型可能会崩溃的。
AppCompatTextView
是没有对CompoundDrawable
进行适配的,所以需要自己动手才能丰衣足食。简单原理是,判断系统版本如果小于5.0,就用ContextCompat.getDrawable
获取到Drawable
实例,再setCompoundDrawablesWithIntrinsicBounds
。
这个部分已经有人做好并开源了,地址:VectorCompatTextView,轻松compile到项目中使用。他还特意添加了一个实用功能——tint染色——可以选择是否让图标与文字颜色一样,这样就不必关心xml里的fillColor属性了。用例:1
2
3
4
5
6
7
8
9
10<com.xw.repo.VectorCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_gray_light"
android:gravity="center_vertical"
android:padding="16dp"
android:text="Next"
android:textSize="16sp"
app:drawableRightCompat="@drawable/svg_ic_arrow_right"
app:tintDrawableInTextColor="true"/>
效果:
MenuItem
MenuItem
就是在res/menu/
目录下通过xml配置的菜单,适用于NavigationView
的menu属性
和Activity
中onCreateOptionsMenu()
注入的选项菜单。
VectorDrawable 转 Bitmap
自定义View中也可以自由使用矢量图。
首先需要将VectorDrawable
转为 Bitmap
,看码:1
2
3
4
5
6
7
8
9
10
11
12
13
14public Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
执行以上方法获得一个Bitmap
的实例(设为mVectorBitmap
),然后再在ondraw()
里根据你的需求画出bitmap
:1
2
3
4
5
6
7
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
///
canvas.drawBitmap(mVectorBitmap, left, top, paint);
///
}